// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*
 * Utf8TextCoder.cpp
 *
 *  Created on: Dec 15, 2009
 *      Author: dhovemey
 */

#include "util/Error.h"
#include "util/InputStream.h"
#include "util/OutputStream.h"
#include "util/TextEncodingException.h"
#include "util/String.h"
#include "util/Utf8TextCoder.h"

Utf8TextCoder::Utf8TextCoder()
	: m_decodeError(false)
	, m_encodeError(false)
{

}

Utf8TextCoder::~Utf8TextCoder()
{

}

long Utf8TextCoder::decode(InputStream *in)
{
	int leadByte = in->read();
	if (leadByte < 0) {
		return -1; // end of file
	}

	if (startOf1ByteSequence(leadByte)) {
		return long(leadByte);
	}

	if (startOf2ByteSequence(leadByte)) {
		return decode2ByteSequence(leadByte, in);
	}

	if (startOf3ByteSequence(leadByte)) {
		return decode3ByteSequence(leadByte, in);
	}

	if (startOf4ByteSequence(leadByte)) {
		return decode4ByteSequence(leadByte, in);
	}

	// invalid encoding
	Error::raise(new TextEncodingException(L"Invalid lead byte in UTF-8 sequence: " + String::asWString(leadByte)));
	m_decodeError = true;
	return -1;
}

bool Utf8TextCoder::encode(wchar_t c, OutputStream *out)
{
	if(requires1ByteSequence(c)) {
		if (!out->write(c)) {
			m_encodeError = true;
		}
	} else if (requires2ByteSequence(c)) {
		encode2ByteSequence(c, out);
	} else if (requires3ByteSequence(c)) {
		encode3ByteSequence(c, out);
	} else if (requires4ByteSequence(c)) {
		encode4ByteSequence(c, out);
	} else {
		Error::raise(new IOException(std::wstring(L"Invalid UCS code point: ") + String::asWString(int32_t(c))));
		m_encodeError = true;
	}
	return !m_encodeError;
}

bool Utf8TextCoder::decodeError()
{
	return m_decodeError;
}

bool Utf8TextCoder::encodeError()
{
	return m_encodeError;
}

int Utf8TextCoder::nextByte(InputStream *in)
{
	int c = in->read();
	if (c < 0) {
		if (in->atEOF()) {
			Error::raise(new TextEncodingException(L"Unexpected EOF in UTF-8 sequence"));
		}
		m_decodeError = true;
	}
	return c;
}

int Utf8TextCoder::getContinuationByte(InputStream *in)
{
	int c = nextByte(in);
	if (c < 0) {
		return c;
	}
	if ((c & 0xC0) != 0x80) {
		Error::raise(new TextEncodingException(L"Invalid continuation byte in UTF-8 sequence: " + String::asWString(c)));
		m_decodeError = true;
		return -1;
	}
	return c;
}

// One-byte encodings:
// 0xxxxxxx

bool Utf8TextCoder::startOf1ByteSequence(int leadByte)
{
	return leadByte < 128;
}

// Two-byte encodings:
// 110yyyxx 10xxxxxx

bool Utf8TextCoder::startOf2ByteSequence(int leadByte)
{
	return (leadByte & 0xE0) == 0xC0;
}

int Utf8TextCoder::decode2ByteSequence(int leadByte, InputStream *in)
{
	int result = 0;

	int c2 = getContinuationByte(in);
	if (c2 < 0) {
		return -1;
	}

	result |= ((leadByte & 0x1F) << 6);  // first byte has bits 6..10
	result |= (c2 & 0x3F);               // second byte has bits 0..5
	return result;
}

// Three-byte encodings:
// 1110yyyy 10yyyyxx 10xxxxxx

bool Utf8TextCoder::startOf3ByteSequence(int leadByte)
{
	return (leadByte & 0xF0) == 0xE0;
}

int Utf8TextCoder::decode3ByteSequence(int leadByte, InputStream *in)
{
	int result = 0;

	int c2 = getContinuationByte(in);
	if (c2 < 0) {
		return -1;
	}
	int c3 = getContinuationByte(in);
	if (c3 < 0) {
		return -1;
	}

	result |= ((leadByte & 0xF0) << 12);  // first byte has bits 12..15
	result |= (c2 & 0x3F) << 6;           // second byte has bits 6..11
	result |= (c3 & 0x3F);                // third byte has bits 0..5
	return result;
}

// Four-byte encodings:
// 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx

bool Utf8TextCoder::startOf4ByteSequence(int leadByte)
{
	return (leadByte & 0xF8) == 0xF0;
}

int Utf8TextCoder::decode4ByteSequence(int leadByte, InputStream *in)
{
	int result = 0;

	int c2 = getContinuationByte(in);
	if (c2 < 0) {
		return -1;
	}
	int c3 = getContinuationByte(in);
	if (c3 < 0) {
		return -1;
	}
	int c4 = getContinuationByte(in);
	if (c4 < 0) {
		return -1;
	}

	result |= ((leadByte & 0x07) << 18);   // first byte has bits 19..21
	result |= ((c2 & 0x3F) << 12);         // second byte has bits 12..17
	result |= ((c3 & 0x3F) << 6);          // third byte has bits 6.11
	result |= (c4 & 0x3F);                 // fourth byte has bits 0..5
	return result;
}

// U+0000–U+007F 0xxxxxxx

bool Utf8TextCoder::requires1ByteSequence(wchar_t c)
{
	return c <= 0x7F;
}

// U+0080–U+07FF 110yyyxx 10xxxxxx

bool Utf8TextCoder::requires2ByteSequence(wchar_t c)
{
	return c >= 0x80 && c <= 0x7FF;
}

bool Utf8TextCoder::encode2ByteSequence(wchar_t c, OutputStream *out)
{
	uint8_t b1 = 0xC0 | ((c >> 6) & 0x1F); // first byte contains bits 6..10
	uint8_t b2 = 0x80 | (c & 0x3F);        // second byte contains bits 0..5

	if ( !(out->write(b1) && out->write(b2)) ) {
		m_encodeError = true;
	}
	return !m_encodeError;
}

// U+0800–U+FFFF 1110yyyy 10yyyyxx 10xxxxxx

bool Utf8TextCoder::requires3ByteSequence(wchar_t c)
{
	return c >= 0x800 && c <= 0xFFFF;
}

bool Utf8TextCoder::encode3ByteSequence(wchar_t c, OutputStream *out)
{
	uint8_t b1 =0xD0 | ((c >> 12) & 0x0F); // first byte contains bits 12..15
	uint8_t b2 =0x80 | ((c >> 6) & 0x3F);  // second byte contains bits 6..11
	uint8_t b3 =0x80 | (c & 0x3F);         // third byte contains bits 0..5

	if ( !(out->write(b1) && out->write(b2) && out->write(b3)) ) {
		m_encodeError = true;
	}
	return !m_encodeError;
}

// U+10000–U+10FFFF 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx

bool Utf8TextCoder::requires4ByteSequence(wchar_t c)
{
	return c >= 0x10000 && c <= 0x10FFFF;
}

bool Utf8TextCoder::encode4ByteSequence(wchar_t c, OutputStream *out)
{
	uint8_t b1 = 0xF0 | ((c >> 18) & 0x07); // first byte has bits 18..20
	uint8_t b2 = 0x80 | ((c >> 12) & 0x3F); // second byte has 12..17
	uint8_t b3 = 0x80 | ((c >> 6) & 0x3F);  // third byte has bits 6..11
	uint8_t b4 = 0x80 | (c & 0x3F);         // fourth byte has bits 0..5

	if ( !(out->write(b1) && out->write(b2) && out->write(b3) && out->write(b4)) ) {
		m_encodeError = true;
	}
	return !m_encodeError;
}

